/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file camera2D.hpp
 * @author TooOld2Rock'nRoll
 * @date 2023/10/02
 * @brief A 2D camera view for the ArcadeFighter library.
 *
 * @todo Create a frustum instead of visible area (it's basically the same, but lets keep the standards...)
 *
 * @see https://learnopengl.com/Getting-started/Coordinate-Systems
 * @see https://learnopengl.com/Getting-started/Camera
 */
#ifndef _CAMERA2D_HPP_
#define _CAMERA2D_HPP_

/*---- Includes ----*/
#include <string>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>


/*---- Class Declaration ----*/
/**
 * @brief A Orthographic View Matrix to simulate a 2D Camera.
 */
class Camera2D
{
    public:
        /** @brief How much of the world is visible through the camera frame.  */
        struct frame_limits
        {
            float f_x1 = 0;///< Left
            float f_y1 = 0;///< Top
            float f_x2 = 0;///< Right
            float f_y2 = 0;///< Bottom
        };//end frame_limits


    private:
        //transformations
        bool b_dirty_flag = true;// Mark that the camera position has been changed.
        glm::vec3 v3_position = glm::vec3 (0.f);// The camera position in world coordinates, [z] is scale!.

        frame_limits _visible_world_limits = {0.f, 0.f, 0.f, 0.f}; //how much of the world is being shown in the frame

        //The View Matrix to be applied over the projection on the shader.
        glm::mat4 m4_view_cash = glm::mat4 (1.f);


    protected:
        unsigned ui_win_width = 0;   ///<window width in pixels.
        unsigned ui_win_height = 0;  ///<window height in pixels.
        float f_win_proportion = 0.f;///<saves the window proportions to avoid calculating it every loop

        unsigned ui_world_width = 0; ///<world width in pixels.
        unsigned ui_world_height = 0;///<world height in pixels.

        void fitToBoundaries (float &x, float &y, float &w, float &h);


    public:
        /** @brief Default Constructor */
        Camera2D () {  }
        /**
         * @brief 2D Camera Constructor.
         *
         * @param win_width - the width in pixels of the camera frame.
         * @param win_height - the height in pixels of the camera frame.
         * @param world_width - the maximum width in pixels the camera may pan out.
         * @param wolrd_height - the maximum height in pixels the camera may pan out.
         */
        Camera2D (unsigned win_width, unsigned win_height, unsigned world_width, unsigned wolrd_height) :
                        ui_win_width(win_width), ui_win_height(win_height),
                        ui_world_width(world_width), ui_world_height(wolrd_height) {  }
        /** @brief Destructor. */
        virtual ~Camera2D () {  }

        glm::mat4 getViewMatrix ();

        void setWindowSize (unsigned win_width, unsigned win_height);
        void setWorldSize (unsigned max_width, unsigned max_height);

        void lookAt (const glm::vec3 &position);
        /** @brief Sets the camera position. */
        void lookAt (float x, float y, float z = 1.f) { this->lookAt (glm::vec3 (x, y, z)); }
        /** @brief Adds or subtracts from the camera current position. */
        void translate (float x, float y, float z = 0.f) { this->lookAt (this->v3_position + glm::vec3 (x, y, z)); }
        /** @brief Gets the camera center focus. */
        const glm::vec3& getPosition () const { return this->v3_position; }
        /** @brief Returns the limits in pixels of the world visible in the camera frame. */
        const frame_limits& getVisibleWorldLimits () const { return this->_visible_world_limits; }

        void fitToWindow (bool center);
        void frameByHeight (float x_center, float y_center, float height);
        void frameByWidth (float x_center, float y_center, float width);
};//END Camera2D

#endif //_CAMERA2D_HPP_

